/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * HipNetNode.cpp
 * An implementation of the Hideously Inadequate Protocol - Controller Side
 * Copyright (C) 2012 Hippy
 * Copyright (C) 2005-2009 Simon Newton
 */

#include <string.h>
#include <algorithm>
#include <map>
#include <string>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#include "ola/Logging.h"
#include "ola/network/NetworkUtils.h"
#include "ola/rdm/RDMCommand.h"
#include "ola/rdm/RDMEnums.h"
#include "ola/rdm/UID.h"
#include "ola/rdm/UIDSet.h"
#include "ola/thread/SchedulerInterface.h"
#include "plugins/hipnet/HipNetNode.h"

namespace ola {
namespace plugin {
namespace hipnet {

using std::string;
using std::map;
using ola::network::HostToNetwork;
using ola::network::NetworkToHost;
using ola::rdm::RDMCommand;
using ola::rdm::RDMRequest;
using ola::rdm::RDMRequest;
using ola::rdm::RDMResponse;
using ola::thread::SchedulerInterface;
using ola::Callback0;
using ola::NewCallback;


const char HipNetNode::NODE_NAME[] = "OLA";


/*
 * Create the controller node
 */
HipNetNode::HipNetNode(PluginAdaptor *plugin_adaptor)
    : m_plugin_adaptor(plugin_adaptor), 
      m_running(false),
      m_options(DEFAULT_OPTIONS),
      m_universe(0),
      m_node_name(NODE_NAME)
     {
}


/*
 * Cleanup
 */
HipNetNode::~HipNetNode() {
  Stop();

  std::map<uint8_t, universe_handler>::iterator iter;
  for (iter = m_handlers.begin(); iter != m_handlers.end(); ++iter) {
    delete iter->second.closure;
  }
  m_handlers.clear();
}


/*
 * Start the HipNet Node
 */
bool HipNetNode::Start() {

  if (m_running)
    return false;

  OLA_DEBUG << "Attaching SHM Segments";

    if (!OpenSHM())
     return false;

  OLA_DEBUG << "Registering timeout PollRX";
  // TODO:  experiment with timeout interval,
  // to find best latency/cpu compromise
  // though this polls at 5ms intervals, it's very fast.
  m_timeout_id = m_plugin_adaptor->RegisterRepeatingTimeout(5,
        NewCallback(this, &HipNetNode::PollRX));
  
  if (m_timeout_id == ola::thread::INVALID_TIMEOUT){
   return false;
  }

  m_running = true;
  return true;
}


/*
 * Stop this node
 */
bool HipNetNode::Stop() {
  if (!m_running)
    return false;

  if (m_timeout_id != ola::thread::INVALID_TIMEOUT) {
    m_plugin_adaptor->RemoveTimeout(m_timeout_id);
    m_timeout_id = ola::thread::INVALID_TIMEOUT;
  }
  
  m_running = false;
  return true;
}


/*
 * Set the closure to be called when we receive data for this universe.
 * @param universe the universe to register the handler for
 * @param handler the Callback0 to call when there is data for this universe.
 * Ownership of the closure is transferred to the node.
 */
bool HipNetNode::SetHandler(uint8_t universe,
                            DmxBuffer *buffer,
                            Callback0<void> *closure) {
  if (!closure)
    return false;

  map<uint8_t, universe_handler>::iterator iter =
    m_handlers.find(universe);

  if (iter == m_handlers.end()) {
    universe_handler handler;
    handler.buffer = buffer;
    handler.closure = closure;
    m_handlers[universe] = handler;
  } else {
    Callback0<void> *old_closure = iter->second.closure;
    iter->second.closure = closure;
    delete old_closure;
  }
  return true;
}


/*
 * Remove the handler for this universe
 * @param universe the universe handler to remove
 * @param true if removed, false if it didn't exist
 */
bool HipNetNode::RemoveHandler(uint8_t universe) {
  map<uint8_t, universe_handler>::iterator iter =
    m_handlers.find(universe);

  if (iter != m_handlers.end()) {
    Callback0<void> *old_closure = iter->second.closure;
    m_handlers.erase(iter);
    delete old_closure;
    return true;
  }
  return false;
}

/*
 * Send some DMX data
 * @param universe - the id of the universe to send to
 * @param buffer - the DMX data
 * @return true if it was sent successfully, false otherwise
 */
bool HipNetNode::SendHipDMX(uint8_t universe, const ola::DmxBuffer &buffer) {
  if (!m_running)
    return false;
  return SendHipData(universe, buffer);
}

/*
 * Poll rx_shm blocks for new RX'd data
 * Assumes OpenSHM has been called.
 */
bool HipNetNode::PollRX(void) {
 static unsigned int last_seq[HIP_XLR_LAST+1];

 for (int cnt = 0; cnt < HIP_XLR_LAST+1; cnt++){
   if ((rx_shm[cnt]->ack) && (last_seq[cnt] != rx_shm[cnt]->seq)) {    
    HandleData(rx_shm[cnt]->data,sizeof(hipnet_data_t),cnt);  // consume the frame
    rx_shm[cnt]->ack = 0;  // ack reception of data after we have it
    last_seq[cnt] = rx_shm[cnt]->seq;
   }   
 } 
 return true;
}


/*
 * Open SHM segments
 */
bool HipNetNode::OpenSHM(void) {
 
//  tx segments are data which is sent to a node, which is then sent out an XLR on the node
 for (int cnt = HIP_XLR_FIRST ; cnt < HIP_XLR_LAST+1; cnt++) { 
    tx_shmkey[cnt] = HIPNET_PORT + cnt;
    
    // create the segment.
    if ((tx_shmid[cnt] = shmget(tx_shmkey[cnt], sizeof(shm_block_t), IPC_CREAT | 0666)) < 0) {
        perror("shmget");
        exit(1);
    }
    // attach to the segment
    if ( (tx_shm[cnt] = (shm_block_t *)shmat(tx_shmid[cnt], NULL, 0)) == (shm_block_t *) -1 ) {
        perror("shmat");
        exit(1);
    }
  // initializing the block is only done by a node - shown here only for reference
/*
   memset(tx_shm[cnt], 0, sizeof(shm_block_t));
   tx_shm[cnt]->data.head = htonl((uint32_t) HIPNET_DMX);
   tx_shm[cnt]->seq = 0;  
*/   
 }

//  rx segments are data received from an XLR, and sent to the controller by the node
 for (int cnt = HIP_XLR_FIRST ; cnt < HIP_XLR_LAST+1; cnt++) { 
    rx_shmkey[cnt] = HIPNET_PORT + (HIP_XLR_LAST+1) + cnt;
    
    // create the segment.
    if ((rx_shmid[cnt] = shmget(rx_shmkey[cnt], sizeof(shm_block_t), IPC_CREAT | 0666)) < 0) {
        perror("shmget");
        exit(1);
    }
    // attach to the segment
    if ( (rx_shm[cnt] = (shm_block_t *) shmat(rx_shmid[cnt], NULL, 0)) == (shm_block_t *) -1 ) {
        perror("shmat");
        exit(1);
    }
 }
    
 return  true;
}






/*
 * Set RX Mode
 * @param universe the id of the universe to set to RX mode
  * @return true if it was send successfully, false otherwise
 */
bool HipNetNode::SetRXMode(uint8_t universe) {
  if (!m_running)
    return false;
   OLA_WARN << "set_RX_mode()";
  return SendSetRX(universe);
}

/*
 * Disable port
 * @param universe the id of the universe to set to disable
  * @return true if it was send successfully, false otherwise
 */
bool HipNetNode::SetDisablePort(uint8_t universe) {
  if (!m_running)
    return false;
   OLA_WARN << "Disable port: " << universe;
  return SendDisablePort (universe);
}




/*
 * Handle an Hip Pong packet.
 * Of very little use other than an connection checking
 */
void HipNetNode::HandlePong(const hipnet_pong_t &pong,
                             ssize_t length) {
  
  if (length < (ssize_t) sizeof(hipnet_pong_t)) {
    OLA_DEBUG << "invalid pong packet received with length: " << length << " < " <<
      sizeof(hipnet_pong_t);
    return;
  }
  
  if (pong.mode == 0x00) {
    OLA_DEBUG << "a hipnet node has request config for universe " << pong.universe;
    
   }
  
  (void) pong;
//  (void) source;
}


/*
 * Handle a HipNet data packet
 */
void HipNetNode::HandleData(const hipnet_data_t &data,
                            ssize_t length,
                            const int universe) {
  
  static const ssize_t header_size = sizeof(hipnet_data_t) - DMX_UNIVERSE_SIZE;

  if (length < header_size) {
    OLA_WARN << "invalid data size, too small " << length << " < " << header_size;
    return;
  }

  if (universe !=  data.universe) {
    OLA_WARN << "unexpected universe, exp: " << universe << " - got: " << data.universe ;
    return;
  }

  map<uint8_t, universe_handler>::iterator iter =
    m_handlers.find(data.universe);

  if (iter == m_handlers.end()) {
    OLA_DEBUG << "Not interested in universe " <<
      static_cast<int>(data.universe) << ", skipping ";
    return;
  }

 // pick the most appropriate size for the packet (detect byte order)
  ssize_t data_size = std::min(length - header_size,
                               (ssize_t) NetworkToHost(data.size));

  switch (data.mode) {
    case 0x00:      // a dmx packet for transmission out an xlr
                            // controller should never receive these packets...
                            // so we ignore
      break;
    case 0x01:  {    // a packet received in from an xlr
      switch (data.start) {
            case 0xcc :     // RDM START code
                 OLA_DEBUG << "rdm rx"; // handle rdm
                break;
                default:  // any other START code                 
                iter->second.buffer->Set(data.data, data_size);
                break;
        } // switch START code   
      break;     
     } 
    case 0xFF:      // timeout frame, expected response to last RDM request was not received.
        // handle rdm timeout
      break;
    default:
      OLA_WARN << "unknown hipnet data type " << data.mode;
      return;
  }
  iter->second.closure->Run();
}


/*
 * Send an HipNet Ping
 */
bool HipNetNode::SendHipPing(uint8_t mode, uint8_t universe_num) {
  hipnet_packet_union_t packet;
  packet.ping.head = HostToNetwork((uint32_t) HIPNET_PING);
  packet.ping.srcnode = HIPNET_CONTROLLER;
  packet.ping.universe = universe_num;      // TODO
  packet.ping.mode = mode;
  return SendPacket(universe_num, packet, sizeof(packet.ping));
}


/*
 * Send a HipNet dmx packet, start TX mode
 */
bool HipNetNode::SendHipData(uint8_t universe,
                             const DmxBuffer &buffer) {
  hipnet_packet_union_t packet;
  memset(&packet.dmx, 0, sizeof(packet.dmx));
  packet.dmx.head = HostToNetwork((uint32_t) HIPNET_DMX);
  packet.dmx.universe = universe;
  packet.dmx.start = 0; //START_CODE;
  packet.dmx.mode = 0x00;   // DMX TX MODE
  unsigned int size = DMX_UNIVERSE_SIZE;
  buffer.Get(packet.dmx.data, &size);
  packet.dmx.size = HostToNetwork((uint16_t) size);
  OLA_WARN << "SendHipData()";
  return SendPacket(universe, packet, sizeof(packet.dmx));
}

/*
 * Set a universe to RX mode
 */
bool HipNetNode::SendSetRX(uint8_t universe) {
  hipnet_packet_union_t packet;
  memset(&packet.dmx, 0, sizeof(packet.dmx));
  packet.dmx.head = HostToNetwork((uint32_t) HIPNET_DMX);
  packet.dmx.universe = universe;
  packet.dmx.start = 0; //START_CODE;
  packet.dmx.mode = 0x01;   // DMX RX MODE
  unsigned int size = 0;
  packet.dmx.size = HostToNetwork((uint16_t) size);
  OLA_WARN << "SendSetRX()";
  return SendPacket(universe, packet, sizeof(packet.dmx));
}

/*
 * Set a universe to RX mode
 */
bool HipNetNode::SendDisablePort(uint8_t universe) {
  hipnet_packet_union_t packet;
  memset(&packet.dmx, 0, sizeof(packet.dmx));
  packet.dmx.head = HostToNetwork((uint32_t) HIPNET_DMX);
  packet.dmx.universe = universe;
  packet.dmx.mode = 0x0F;   // Disable port
  packet.dmx.start = 0; //START_CODE;
  unsigned int size = 0;
  packet.dmx.size = HostToNetwork((uint16_t) size);
  OLA_WARN << "SendDisablePort()";
  return SendPacket(universe, packet, sizeof(packet.dmx));
}


/*
 * Send an HipNet packet
 */
bool HipNetNode::SendPacket(const int xlr_num,
                            const hipnet_packet_union_t &packet,
                            unsigned int size) {
 int loopcnt = 0;

 while (tx_shm[xlr_num]->ack && (loopcnt++ < 5)) {
  OLA_WARN << "waiting for ACK=0 (" << loopcnt << ")";
  usleep(1000);
 }
 
 memcpy(&tx_shm[xlr_num]->data, 
   reinterpret_cast<const uint8_t*>(&packet.dmx),
       size);
 tx_shm[xlr_num]->seq += 1;
 tx_shm[xlr_num]->ack = 0;
  
  return true;
}
}  // hipnet
}  // plugin
}  // ola
